﻿

#define GLEW_STATIC

#include <iostream>
#include <string>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "common.h"
#include "Shader.h"
#include "stb_image.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


using namespace std;

/*
 * // ---------------------- 本章理论知识 ------------------------- 
 * 1.思考，如果我们想要移动2D或者3D的图形,总不能每次通过改变顶点数据在灌入缓存区得方式来移动我们得图形吧？
 *		A.灵魂拷问：每次改变顶点数据，那数据量得多少？
 *		B.灵魂拷问：大家都玩过游戏，游戏里面得人物都是活动，活动得每一帧过程如果都时顶点数据保存？那数据量又该得时多少？
 *		C.解决方案：不存储顶点数据，只记录向量信息（也即是我的移动信息），这样通过向量直接的计算直接得出下一帧画面中图形所在的顶点位置。
 * 2.思考：向量与向量之间的计算是作为类人的方式来思考该如何计算向量，对于计算机来说可不能理解，那计算机该怎么计算呢?
 *		A.思路起点1：计算机能很方便的实现矩阵计算 
 *		B.思路起点2：而矩阵跟向量之间是可以在一定规范下之间之间进行计算。
 *		C.解决方案:综合上述两点，要进行向量之间的运算时，我可以先转为矩阵计算的方式让我计算机帮我算出结果。
 *
 * 3.向量与矩阵计算的差异性：
 *		------------- 向量 ---------------
 *		1.向量与标量相加，向量中每一个分量都加一次标量
 *		2.向量取反（结果是方向直接相反）
 *		3.向量与向量相加，第一个向量中每一个分量加上另外一个向量中对应的分量
 *		4.向量与向量相减，第二个向量中每一个分量减去另外一个向量中对应的分量（也可以理解为加上另外一个向量中对应分量取反的值）
 *		5.向量的长度计算：将向量中Y看做直角三角形的勾边，将向量中的X看作直角三角形的股边，然后根据勾股定理可以计算出向量的长度（起始点到向量坐标点的长度）
 *			有个特殊的向量叫单位（unit vector）向量,有个特别的性质就是该向量的长度为1，具体有啥用后续补充。。。
 *		6.两个向量相乘本身是未定义的一个概念，它在视觉上来说无任何意思，但实在要做相乘，可以有两种选择：
 *			A.点乘方式：理论滤过（两个数乘结果再乘以两个向量直接夹角的余弦值），可以很方便的得出两个向量是否正交或者平行（后续计算光照有用）
 *			B.叉乘方式：
 *				a.存在条件限制（3维度空间、2个向量必须是不平行的）
 *				b.计算公式复杂或者说有些反直觉，所以初期学习记下计算公式即可(个人推测算的简便算法：2|3|1 * 3|1|2 - 3|1|2 * 2|3|1  )
 *				c.将计算出一个正交于两个输入向量的第三个向量
 *		------------- 矩阵 ---------------		
 *		1.矩阵与标量相加/减，矩阵中每一个元素都加/减一次标量 （提示：数学上没有矩阵加向量的说法，但很多计算机线性代数有关的库都支持，比如我们要使用的GLM）
 *		2.矩阵与矩阵相加/减，第一个矩阵中每一个元素都加/减第二个矩阵中的对应的元素（矩阵加减需要保证是同维度的矩阵，否在无法进行计算）
 *		3.矩阵的数乘，和矩阵与标量的加减一样，该数字分别乘以矩阵的每一个元素即可（数乘可做缩放处理)
 *		4.矩阵相乘，
			存在2个规范限制：
 *				A.只有当左侧矩阵的列数与右侧矩阵的行数相等，两个矩阵才能相乘.
 *				B.矩阵相乘不遵守交换律(Commutative)，也就是说A⋅B != B⋅A。
 *			矩阵公式为： 第一个矩阵中一个元素所在行的所有元素分别乘以第二个矩阵中对应第一个矩阵元素所在列的对应元素并相加，很混乱是吧？ 我们以2个2X2的矩阵
 *				A矩阵的值为 {1,2} ， B矩阵的值为{2,4}, 这两个矩阵乘得出C矩阵的过程：
 *							{3,4}			    {6,8}
 *				C矩阵中第一行第一个元素的值为：  A[1,1] * B[1,1] + A[1,2] * B[2,1] = 1 * 2 + 2 * 6 = 14 
 *				C矩阵中第一行第二个元素的值为：  A[1,1] * B[1,2] + A[1,2] * B[2,2] = 1 * 4 + 2 * 8 = 20
 *				C矩阵中第二行第一个元素的值为：  A[2,1] * B[2,1] + A[2,2] * B[2,1] = 3 * 2 + 4 * 6 = 30
 *				C矩阵中第二行第二个元素的值为：  A[2,1] * B[1,2] + A[2,2] * B[2,2] = 3 * 4 + 4 * 8 = 48
 *				最终C矩阵的结果为： {14,20}
 *									{30,48}
 *			结果矩阵的维度是(n, m)，n等于左侧矩阵的行数，m等于右侧矩阵的列数。
 *		------------- 矩阵与向量相乘 ---------------		
 *		1.可以将向量看作是N * 1的矩阵（N标识维度，1标识每个维度我就1个值）
 *		2.基于矩阵相乘的规范限制要求， 那我可以找一个矩阵（? * M），只要我找的矩阵列数（M）与向量矩阵行数（N）对应就可以进行矩阵运算 
			PS。为什么要关心矩阵*向量，因为很多2D 3D图形位移、缩放、旋转可以放在矩阵中计算，通过矩阵*向量可以改变我们的向量。
 *		3.在OpenGl中通常最多使用4*4的变换矩阵就可以解决大部分得计算问题，因为OpenGl中大部分向量都是4分量得。（这个 4*4的矩阵就是我上一点提到的我要找那个矩阵）
 *		  单位矩阵是一个除了对角线以外所有值都是0的矩阵。
 *		4.有了单位矩阵这个起点，我们就是可以诸如缩放、唯一、旋转等计算（这里原理查看网上教程）。
 */
namespace A0501
{

	// 定义顶点数据组
	float vertices[] = {
		//---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
		0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
		0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
	};

	// 顶点数据索引目录
	unsigned int indices[] = {
		0,1,2,
		0,2,3
	};

	// 处理窗口输入函数
	void processInput(GLFWwindow* window);

	int execute(Transform transform)
	{
		// 初始化 GLFW 环境
		glfwInit();

		// 提示即将创建窗口时我将使用OpenGL的 主版本号、次版本号及采用哪种配置方式
		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
		glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

		// 创建我要使用的窗口
		// 大小：800 X 600 
		// 标题：My OpenGl Learn
		// 
		GLFWwindow* window = glfwCreateWindow(800, 600, "My OpenGl Learn", NULL, NULL);
		if (window == NULL)
		{
			printf("Open Window Failed");
			glfwTerminate();
			return -1;
		}

		// 将之前成功创建出来的window对象作为后续OpenGl整个上下文环境中的操作对象 
		glfwMakeContextCurrent(window);

		// 初始化 GLEW 环境
		glewExperimental = true;
		if (glewInit() != GLEW_OK)
		{
			printf("Init GLEW Failed");
			glfwTerminate();
			return -1;
		}

		// 设置OpenGl绘制图形时,在刚才指定上下文环境window窗口中的视图大小
		glViewport(0, 0, 800, 600);


		// / ===================================   01 创建所需的VAO、VBO、EBO对象并绑定数据  =================================
		// 定义及绑定所需的VAO（顶点数组对象）
		unsigned int VAO;
		glGenVertexArrays(1, &VAO);
		glBindVertexArray(VAO);

		// 定于及绑定所需的VBO（顶点缓存数组）
		unsigned int VBO;
		glGenBuffers(1, &VBO);
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		// 复制顶点数组到缓冲中供OpenGL使用(为绑定的VBO对象灌入我们用户自己定义的顶点数据)
		glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

		// 定义及绑定EBO（索引缓存对象）
		unsigned int EBO;
		glGenBuffers(1, &EBO);
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
		// 复制顶点索引数组到缓存中共OpenGL使用（为绑定的对象灌入我们用户自己定义的顶点索引数据）
		glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

		// 解析顶点属性：使用0号、1号、2号顶点位置来分为存放取到的坐标信息、颜色信息以及纹理信息（本质就是告诉OpenGl如何解析这个数据）
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
		glEnableVertexAttribArray(1);
		glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
		glEnableVertexAttribArray(2);



		// 创建纹理对象
		unsigned int textureA;
		glGenTextures(1, &textureA);
		unsigned int textureB;
		glGenTextures(1, &textureB);


		// 图形翻转处理
		stbi_set_flip_vertically_on_load(true);

		// 读取纹理图片数据
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, textureA);
		int width, height, nrChannels;
		unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
		if (data)
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
			glGenerateMipmap(GL_TEXTURE_2D);
		}
		else
		{
			cout << "load file error." << endl;
		}
		stbi_image_free(data);

		glActiveTexture(GL_TEXTURE2);
		glBindTexture(GL_TEXTURE_2D, textureB);
		unsigned char* data2 = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
		if (data2)
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
			glGenerateMipmap(GL_TEXTURE_2D);
		}
		else
		{
			cout << "load file error." << endl;
		}
		stbi_image_free(data2);


		// 使用自定的Shader
		Shader* myShader = new Shader("vs/5.1.vert", "fs/5.1.frag");

		// 提供三种变化及组合选择,
		glm::mat4 trans;
		switch (transform)
		{
			case Transform::Translate:
				trans = glm::translate(trans, glm::vec3(0.5f, 0.5f, 0.0f));
				break;
			case Transform::Rotate:
				trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
				break;
			case Transform::Scale:
				trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
				break;
			case Transform::Combine:
				// 注意: 这里需要反向理解，这里程序书写顺序为位移、旋转、缩放，但实际矩阵的计算顺序为 缩放、旋转、位移
				// 因矩阵乘法不遵守“交互率”（A*B != B*A），所以计算顺序改变会导致计算的结果存在问题（切不可搞反）
				// 矩阵计算正确的变化顺序就是：缩放、旋转、位移 ，接下来再本节的课后作业会体现出来
				trans = glm::translate(trans, glm::vec3(0.5f, 0.5f, 0.0f));
				trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
				trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));
				break;
		}


		// 如果窗口未关闭，则一直进行窗口渲染循环（事件循环）
		while (!glfwWindowShouldClose(window))
		{
			// 处理用户输入
			processInput(window);

			// 渲染之前先清屏处理
			glClearColor(0, 0.5f, 0.5f, 1.0f);
			glClear(GL_COLOR_BUFFER_BIT);

			// 执行渲染指令
			glBindVertexArray(VAO);
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
			myShader->use();

			glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 1);
			glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 2);
			glUniformMatrix4fv(glGetUniformLocation(myShader->ID, "transform"), 1, GL_FALSE, glm::value_ptr(trans));

			glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

			// 检查交换事件&交换缓存
			glfwSwapBuffers(window);
			glfwPollEvents();
		}
		glfwTerminate();

		system("pause");
	}

	void processInput(GLFWwindow* window)
	{
		if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		{
			glfwSetWindowShouldClose(window, true);
		}
	};
}